{
 "cells": [
  {
   "cell_type": "code",
   "execution_count": 1,
   "metadata": {},
   "outputs": [],
   "source": [
    "import tensorflow as tf"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "'2.0.0-beta1'"
      ]
     },
     "execution_count": 2,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "tf.__version__"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# Tensorflow 安装与配置"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Tensorflow初体验"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 3,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "tf.Tensor(\n",
      "[[19 22]\n",
      " [43 50]], shape=(2, 2), dtype=int32)\n"
     ]
    }
   ],
   "source": [
    "A = tf.constant([[1, 2], [3, 4]])\n",
    "B = tf.constant([[5, 6], [7, 8]])\n",
    "C = tf.matmul(A, B)\n",
    "\n",
    "print(C)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# Tensorflow 基础"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 4,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "<tf.Tensor: id=10, shape=(), dtype=float32, numpy=0.5348927>"
      ]
     },
     "execution_count": 4,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "# 定义一个随机数（标量）\n",
    "random_float = tf.random.uniform(shape=())\n",
    "random_float"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 5,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "<tf.Tensor: id=14, shape=(5,), dtype=float32, numpy=array([0., 0., 0., 0., 0.], dtype=float32)>"
      ]
     },
     "execution_count": 5,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "# 定义一个有5个元素的零向量\n",
    "zero_vector = tf.zeros(shape=(5))\n",
    "zero_vector"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 6,
   "metadata": {},
   "outputs": [],
   "source": [
    "# 定义两个2×2的常量矩阵\n",
    "A = tf.constant([[1., 2.], [3., 4.]])\n",
    "B = tf.constant([[5., 6.], [7., 8.]])"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 7,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "(2, 2)\n",
      "<dtype: 'float32'>\n",
      "[[1. 2.]\n",
      " [3. 4.]]\n"
     ]
    }
   ],
   "source": [
    "print(A.shape)      # 输出(2, 2)，即矩阵的长和宽均为2\n",
    "print(A.dtype)      # 输出<dtype: 'float32'>\n",
    "print(A.numpy())    # 输出[[1. 2.]\n",
    "            #    [3. 4.]]"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 加减乘除"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 11,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "<tf.Tensor: id=25, shape=(2, 2), dtype=float32, numpy=\n",
       "array([[ 6.,  8.],\n",
       "       [10., 12.]], dtype=float32)>"
      ]
     },
     "execution_count": 11,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "# 计算矩阵A和B的和\n",
    "C = tf.add(A, B)   \n",
    "# 计算矩阵A和B的乘积 \n",
    "D = tf.matmul(A, B) \n",
    "C"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 12,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "<tf.Tensor: id=26, shape=(2, 2), dtype=float32, numpy=\n",
       "array([[19., 22.],\n",
       "       [43., 50.]], dtype=float32)>"
      ]
     },
     "execution_count": 12,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "D"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 8,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "<tf.Tensor: id=19, shape=(2, 2), dtype=float32, numpy=\n",
       "array([[-4., -4.],\n",
       "       [-4., -4.]], dtype=float32)>"
      ]
     },
     "execution_count": 8,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "tf.subtract(A,B)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": []
  },
  {
   "cell_type": "code",
   "execution_count": 9,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "<tf.Tensor: id=21, shape=(2, 2), dtype=float32, numpy=\n",
       "array([[0.2       , 0.33333334],\n",
       "       [0.42857143, 0.5       ]], dtype=float32)>"
      ]
     },
     "execution_count": 9,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "tf.divide(A,B)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 函数$y=x^2$梯度求导当x=3时的导数"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 13,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "[<tf.Tensor: id=38, shape=(), dtype=float32, numpy=9.0>, <tf.Tensor: id=42, shape=(), dtype=float32, numpy=6.0>]\n"
     ]
    }
   ],
   "source": [
    "#x=3\n",
    "x = tf.Variable(initial_value=3.)\n",
    "\n",
    "with tf.GradientTape() as tape:     # 在 tf.GradientTape() 的上下文内，所有计算步骤都会被记录以用于求导\n",
    "    y = tf.square(x)\n",
    "y_grad = tape.gradient(y, x)        # 计算y关于x的导数\n",
    "print([y, y_grad])"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 函数$L(w,b)=||Xw+b-y||^2$求导"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 9,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "[125.0, array([[ 70.],\n",
      "       [100.]], dtype=float32), 30.0]\n"
     ]
    }
   ],
   "source": [
    "X = tf.constant([[1., 2.], [3., 4.]])\n",
    "y = tf.constant([[1.], [2.]])\n",
    "w = tf.Variable(initial_value=[[1.], [2.]])\n",
    "b = tf.Variable(initial_value=1.)\n",
    "with tf.GradientTape() as tape:\n",
    "    L = tf.reduce_sum(tf.square(tf.matmul(X, w) + b - y))\n",
    "w_grad, b_grad = tape.gradient(L, [w, b])        # 计算L(w, b)关于w, b的偏导数\n",
    "print([L.numpy(), w_grad.numpy(), b_grad.numpy()])"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 线性回归"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 11,
   "metadata": {},
   "outputs": [],
   "source": [
    "import numpy as np\n",
    "\n",
    "X_raw = np.array([2013, 2014, 2015, 2016, 2017], dtype=np.float32)\n",
    "y_raw = np.array([12000, 14000, 15000, 16500, 17500], dtype=np.float32)\n",
    "\n",
    "X = (X_raw - X_raw.min()) / (X_raw.max() - X_raw.min())\n",
    "y = (y_raw - y_raw.min()) / (y_raw.max() - y_raw.min())"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "构建$y=ax+b$的函数，损失函数是$L(a,b) = (\\hat y - y)^2$"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 12,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "0.9817775010317537 0.054568006679415956\n"
     ]
    }
   ],
   "source": [
    "a, b = 0, 0\n",
    "num_epoch = 10000\n",
    "learning_rate = 1e-3\n",
    "for e in range(num_epoch):\n",
    "    # 手动计算损失函数关于自变量（模型参数）的梯度\n",
    "    y_pred = a * X + b\n",
    "    grad_a, grad_b = 2*np.dot((y_pred - y),X), 2*np.sum(y_pred - y)\n",
    "\n",
    "    # 更新参数\n",
    "    a, b = a - learning_rate * grad_a, b - learning_rate * grad_b\n",
    "\n",
    "print(a, b)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 13,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "<tf.Variable 'Variable:0' shape=() dtype=float32, numpy=0.9817748> <tf.Variable 'Variable:0' shape=() dtype=float32, numpy=0.0545703>\n"
     ]
    }
   ],
   "source": [
    "X = tf.constant(X)\n",
    "y = tf.constant(y)\n",
    "\n",
    "a = tf.Variable(initial_value=0.)\n",
    "b = tf.Variable(initial_value=0.)\n",
    "variables = [a, b]\n",
    "\n",
    "num_epoch = 10000\n",
    "optimizer = tf.keras.optimizers.SGD(learning_rate=1e-3)\n",
    "for e in range(num_epoch):\n",
    "    # 使用tf.GradientTape()记录损失函数的梯度信息\n",
    "    with tf.GradientTape() as tape:\n",
    "        y_pred = a * X + b\n",
    "        loss = tf.reduce_sum(tf.square(y_pred - y))\n",
    "    # TensorFlow自动计算损失函数关于自变量（模型参数）的梯度\n",
    "    grads = tape.gradient(loss, variables)\n",
    "    # TensorFlow自动根据梯度更新参数\n",
    "    optimizer.apply_gradients(grads_and_vars=zip(grads, variables))\n",
    "\n",
    "print(a, b)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# Tensorflow模型构建与训练"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 14,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "[<tf.Variable 'linear/dense/kernel:0' shape=(3, 1) dtype=float32, numpy=\n",
      "array([[0.40784496],\n",
      "       [1.191065  ],\n",
      "       [1.9742855 ]], dtype=float32)>, <tf.Variable 'linear/dense/bias:0' shape=(1,) dtype=float32, numpy=array([0.78322077], dtype=float32)>]\n"
     ]
    }
   ],
   "source": [
    "\n",
    "X = tf.constant([[1.0, 2.0, 3.0], [4.0, 5.0, 6.0]])\n",
    "y = tf.constant([[10.0], [20.0]])\n",
    "\n",
    "#y=ax+b\n",
    "class Linear(tf.keras.Model):\n",
    "    def __init__(self):\n",
    "        super().__init__()\n",
    "        self.dense = tf.keras.layers.Dense(\n",
    "            units=1,\n",
    "            activation=None,\n",
    "            kernel_initializer=tf.zeros_initializer(),\n",
    "            bias_initializer=tf.zeros_initializer()\n",
    "        )\n",
    "\n",
    "    def call(self, input):\n",
    "        output = self.dense(input)\n",
    "        return output\n",
    "\n",
    "\n",
    "# 以下代码结构与前节类似\n",
    "model = Linear()\n",
    "optimizer = tf.keras.optimizers.SGD(learning_rate=0.01)\n",
    "for i in range(100):\n",
    "    with tf.GradientTape() as tape:\n",
    "        y_pred = model(X)      # 调用模型 y_pred = model(X) 而不是显式写出 y_pred = a * X + b\n",
    "        loss = tf.reduce_mean(tf.square(y_pred - y))\n",
    "    grads = tape.gradient(loss, model.variables)    # 使用 model.variables 这一属性直接获得模型中的所有变量[a,b]\n",
    "    optimizer.apply_gradients(grads_and_vars=zip(grads, model.variables))\n",
    "print(model.variables)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# Tensorfow 常用模块"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 15,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "'./save/model.ckpt-1'"
      ]
     },
     "execution_count": 15,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "checkpoint = tf.train.Checkpoint(model=model)\n",
    "checkpoint.save(\"./save/model.ckpt\")"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 加载模型"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 16,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "<tensorflow.python.training.tracking.util.CheckpointLoadStatus at 0x7f5441361278>"
      ]
     },
     "execution_count": 16,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "model = Linear()\n",
    "checkpoint = tf.train.Checkpoint(model=model)\n",
    "checkpoint.restore(tf.train.latest_checkpoint(\"./save\"))"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 17,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "array([[18.616726],\n",
       "       [20.215637]], dtype=float32)"
      ]
     },
     "execution_count": 17,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "X = tf.constant([[1.0, 8.0, 4.0], [4.0, 5.0, 6.0]])\n",
    "test_pred = model.predict(X)\n",
    "test_pred"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": []
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## TensorBoard可视化"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 18,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "[<tf.Variable 'linear_2/dense_2/kernel:0' shape=(3, 1) dtype=float32, numpy=\n",
      "array([[ 1.9571737 ],\n",
      "       [-0.03056878],\n",
      "       [ 2.0183392 ]], dtype=float32)>, <tf.Variable 'linear_2/dense_2/bias:0' shape=(1,) dtype=float32, numpy=array([0.21406719], dtype=float32)>]\n"
     ]
    }
   ],
   "source": [
    "summary_writer = tf.summary.create_file_writer('./tensorboard')\n",
    "model = Linear()\n",
    "optimizer = tf.keras.optimizers.SGD(learning_rate=0.01)\n",
    "for i in range(100):\n",
    "    with tf.GradientTape() as tape:\n",
    "        y_pred = model(X)      # 调用模型 y_pred = model(X) 而不是显式写出 y_pred = a * X + b\n",
    "        loss = tf.reduce_mean(tf.square(y_pred - y))\n",
    "        with summary_writer.as_default():\n",
    "            tf.summary.scalar(\"loss\", loss, step=i)\n",
    "\n",
    "    grads = tape.gradient(loss, model.variables)    # 使用 model.variables 这一属性直接获得模型中的所有变量\n",
    "    optimizer.apply_gradients(grads_and_vars=zip(grads, model.variables))\n",
    "print(model.variables)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 19,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "TensorBoard 1.14.0a20190603 at http://hcq:6006/ (Press CTRL+C to quit)\n",
      "^C\n"
     ]
    }
   ],
   "source": [
    "!tensorboard --logdir=./tensorboard "
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "#accuracy"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 21,
   "metadata": {},
   "outputs": [],
   "source": [
    "\n",
    "summary_writer = tf.summary.create_file_writer('./tensorboard')\n",
    "#开启Trace，可以记录图结构和profile信息\n",
    "tf.summary.trace_on(graph=True, profiler=True)\n",
    "model = Linear()\n",
    "optimizer = tf.keras.optimizers.SGD(learning_rate=0.01)\n",
    "for i in range(100):\n",
    "    with tf.GradientTape() as tape:\n",
    "        y_pred = model(X)      # 调用模型 y_pred = model(X) 而不是显式写出 y_pred = a * X + b\n",
    "        loss = tf.reduce_mean(tf.square(y_pred - y))\n",
    "        with summary_writer.as_default():\n",
    "            tf.summary.scalar(\"loss\", loss, step=i)\n",
    "\n",
    "    grads = tape.gradient(loss, model.variables)    # 使用 model.variables 这一属性直接获得模型中的所有变量\n",
    "    optimizer.apply_gradients(grads_and_vars=zip(grads, model.variables))\n",
    "with summary_writer.as_default():\n",
    "    tf.summary.trace_export(name=\"model_trace\", step=0, profiler_outdir='./log_dir')    # 保存Trace信息到文件（可选）\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "!tensorboard --logdir=./log_dir "
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": []
  },
  {
   "cell_type": "code",
   "execution_count": 25,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "tf.Tensor([2013 2014], shape=(2,), dtype=int32) tf.Tensor([12000 14000], shape=(2,), dtype=int32)\n",
      "tf.Tensor([2015 2016], shape=(2,), dtype=int32) tf.Tensor([15000 16500], shape=(2,), dtype=int32)\n",
      "tf.Tensor([2017], shape=(1,), dtype=int32) tf.Tensor([17500], shape=(1,), dtype=int32)\n"
     ]
    }
   ],
   "source": [
    "import tensorflow as tf\n",
    "import numpy as np\n",
    "\n",
    "X = tf.constant([2013, 2014, 2015, 2016, 2017])\n",
    "Y = tf.constant([12000, 14000, 15000, 16500, 17500])\n",
    "\n",
    "# 也可以使用NumPy数组，效果相同\n",
    "# X = np.array([2013, 2014, 2015, 2016, 2017])\n",
    "# Y = np.array([12000, 14000, 15000, 16500, 17500])\n",
    "#dataset = tf.data.Dataset.from_tensor_slices((X, Y))\n",
    "\n",
    "dataset = tf.data.Dataset.from_tensor_slices((X, Y)).batch(2)\n",
    "\n",
    "#min-batch SGD\n",
    "for x, y in dataset:\n",
    "    print(x, y) "
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": []
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## @tf.function"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 1,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "14.47761869430542\n"
     ]
    }
   ],
   "source": [
    "import tensorflow as tf\n",
    "import time\n",
    "start_time=time.time()\n",
    "\n",
    "\n",
    "class Linear(tf.keras.Model):\n",
    "    def __init__(self):\n",
    "        super().__init__()\n",
    "        self.dense = tf.keras.layers.Dense(\n",
    "            units=1,\n",
    "            activation=None,\n",
    "            kernel_initializer=tf.zeros_initializer(),\n",
    "            bias_initializer=tf.zeros_initializer()\n",
    "        )\n",
    "\n",
    "    def call(self, input):\n",
    "        output = self.dense(input)\n",
    "        return output\n",
    "optimizer = tf.keras.optimizers.SGD(learning_rate=0.01)\n",
    "X = tf.constant([[1.0, 2.0, 3.0], [4.0, 5.0, 6.0]])\n",
    "y = tf.constant([[10.0], [20.0]])\n",
    "model = Linear()\n",
    "def train_on_step(X,y):\n",
    "    with tf.GradientTape() as tape:\n",
    "        y_pred = model(X)      # 调用模型 y_pred = model(X) 而不是显式写出 y_pred = a * X + b\n",
    "        loss = tf.reduce_mean(tf.square(y_pred - y))\n",
    "    grads = tape.gradient(loss, model.variables)    # 使用 model.variables 这一属性直接获得模型中的所有变量\n",
    "    optimizer.apply_gradients(grads_and_vars=zip(grads, model.variables))\n",
    "        \n",
    "for i in range(10000):\n",
    "    train_on_step(X,y)\n",
    "    \n",
    "end_time=time.time()\n",
    "print(end_time-start_time)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 1,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "2.7334132194519043\n"
     ]
    }
   ],
   "source": [
    "import tensorflow as tf\n",
    "import time\n",
    "start_time=time.time()\n",
    "\n",
    "\n",
    "class Linear(tf.keras.Model):\n",
    "    def __init__(self):\n",
    "        super().__init__()\n",
    "        self.dense = tf.keras.layers.Dense(\n",
    "            units=1,\n",
    "            activation=None,\n",
    "            kernel_initializer=tf.zeros_initializer(),\n",
    "            bias_initializer=tf.zeros_initializer()\n",
    "        )\n",
    "\n",
    "    def call(self, input):\n",
    "        output = self.dense(input)\n",
    "        return output\n",
    "optimizer = tf.keras.optimizers.SGD(learning_rate=0.01)\n",
    "import time\n",
    "start_time=time.time()\n",
    "\n",
    "X = tf.constant([[1.0, 2.0, 3.0], [4.0, 5.0, 6.0]])\n",
    "y = tf.constant([[10.0], [20.0]])\n",
    "model = Linear()\n",
    "@tf.function\n",
    "def train_on_step(X,y):\n",
    "    with tf.GradientTape() as tape:\n",
    "        y_pred = model(X)      # 调用模型 y_pred = model(X) 而不是显式写出 y_pred = a * X + b\n",
    "        loss = tf.reduce_mean(tf.square(y_pred - y))\n",
    "    grads = tape.gradient(loss, model.variables)    # 使用 model.variables 这一属性直接获得模型中的所有变量\n",
    "    optimizer.apply_gradients(grads_and_vars=zip(grads, model.variables))\n",
    "        \n",
    "for i in range(10000):\n",
    "    train_on_step(X,y)\n",
    "    \n",
    "end_time=time.time()\n",
    "print(end_time-start_time)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": []
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.6.8"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 2
}
